// OLD, FOR REF: 
// gcc -Wall -o osx_event_tap.so osx_event_tap.c -framework ApplicationServices -framework Python


// Complile using the following command line:
// python2.5 setup_sysevents_osx.py build
// You must have a link to:
//   build/lib.macosx-10.5-i386-2.5/_sysevents_osx.so
// in the install dir/
// Test with:
// python2.5 -c "import _sysevents_osx; print _sysevents_osx.test(2)"

#include <ApplicationServices/ApplicationServices.h>
#include <Python/Python.h>

static CGRect           screenBounds;
static CFMachPortRef    eventTap;

static PyObject *key_down_callback = NULL;
static PyObject *key_up_callback = NULL;

// This callback will be invoked every time the mouse moves.
//
CGEventRef
myMouseCGEventCallback(CGEventTapProxy proxy, CGEventType type,
                  CGEventRef event, void *refcon)
{
    // Do some sanity check.
    if (type != kCGEventMouseMoved)
        return event;

    // The incoming mouse position.
    CGPoint location = CGEventGetLocation(event);

    // We can change aspects of the mouse event.
    // For example, we can use CGEventSetLoction(event, newLocation).
    // Here, we just print the location.
    printf("(%f, %f)\n", location.x, location.y);

    // We must return the event for it to be useful.
    return event;
}

// This callback will be invoked every time the mouse moves.
//
CGEventRef
myKeyCGEventCallback(CGEventTapProxy proxy, CGEventType type,
                  CGEventRef event, void *refcon)
{
    fprintf(stderr, "key\n");
    CGKeyCode keycode = (CGKeyCode)CGEventGetIntegerValueField(
        event, kCGKeyboardEventKeycode
    );   
    printf("  code=%i\n", keycode);
    
    
    PyObject *arglist;
    PyObject *result;

    /* Time to call the callback */
    fprintf(stderr, "A\n");
    arglist = Py_BuildValue("(i)", (int)keycode);
    fprintf(stderr, "B\n");
    result = PyEval_CallObject(key_down_callback, arglist);
    fprintf(stderr, "C\n");
    Py_DECREF(arglist);

    if (result == NULL)
        return NULL; /* Pass error back */

    Py_DECREF(result);

    return event;
}

int
create_trap(void)
{
    //CFMachPortRef      eventTap;
    CGEventMask        eventMask;
    CFRunLoopSourceRef runLoopSource;

    // The screen size of the primary display.
    screenBounds = CGDisplayBounds(CGMainDisplayID());
    printf("The main screen is %dx%d\n", (int)screenBounds.size.width,
           (int)screenBounds.size.height);


/*
    // Create an event tap for mouse movements.
    eventMask = (1 << kCGEventMouseMoved);
    eventTap = CGEventTapCreate(
                   kCGSessionEventTap, kCGHeadInsertEventTap,
                   0, eventMask, myMouseCGEventCallback, NULL);
    if (!eventTap) {
        fprintf(stderr, "failed to create mouse move event tap\n");
        exit(1);
    }

    // Create a run loop source.
    runLoopSource = CFMachPortCreateRunLoopSource(
                        kCFAllocatorDefault, eventTap, 0);

    // Add to the current run loop.
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource,
                       kCFRunLoopCommonModes);

    // Enable the event tap.
    CGEventTapEnable(eventTap, true);

*/


    // Create an event tap for key events.
    eventMask = (1 << kCGEventKeyUp);
    //eventMask = CGEventMaskBit(kCGEventKeyUp);
    eventTap = CGEventTapCreate(
                   kCGSessionEventTap, kCGHeadInsertEventTap,
                   //kCGHIDEventTap,kCGHeadInsertEventTap,
                   0,
                   //kCGEventTapOptionListenOnly,
                   eventMask,
                   myKeyCGEventCallback, NULL);
    if (!eventTap) {
        fprintf(stderr, "failed to create key event tap\n");
        return 1;
    }
    fprintf(stderr, "key event taped\n");

    // Create a run loop source.
    runLoopSource = CFMachPortCreateRunLoopSource(
                        kCFAllocatorDefault, eventTap, 0);

    // Add to the current run loop.
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource,
                       kCFRunLoopCommonModes);

    // Enable the event tap.
    CGEventTapEnable(eventTap, true);





    // Set it all running.
    //CFRunLoopRun();

    // In a real program, one would have arranged for cleaning up.
    //exit(0);
    return 0;
}

int release_trap(void)
{
    CFRelease(eventTap);
    return 0;
}

// ##########################################################################
// ##########################################################################
// ##########################################################################
// ##########################################################################


static char start_docstring[] = "\
start(key_down_callback) -> None \n\
\n\
Start receiving system key events.\
One MUST call stop after a successfull call to start(...).\
";
static PyObject*
start(PyObject* self, PyObject* args)
{
    PyObject *result = NULL;
    PyObject *temp;

    if (PyArg_ParseTuple(args, "O:start", &temp)) {
        if (!PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "parameter must be callable");
            return NULL;
        }
        Py_XINCREF(temp);               /* Add a reference to new callback */
        Py_XDECREF(key_down_callback);  /* Dispose of previous callback */
        key_down_callback = temp;       /* Remember new callback */
        /* Boilerplate to return "None" */
        Py_INCREF(Py_None);
        result = Py_None;
    }

    if (result)
    {
        create_trap();
    }
    return result;
}

static char stop_docstring[] = "\
stop() -> None \n\
\n\
Stop receiving system key events.\
See also start(...).\
";
static PyObject*
stop(PyObject* self, PyObject* args)
{
    if (!PyArg_ParseTuple(args, ""))
    {
        PyErr_SetString(PyExc_TypeError, "No parameters needed.");
        return NULL;
    }
    release_trap();
    Py_RETURN_NONE;
}


static PyMethodDef _sysevents_osx_methods[] = {
	{"start", (PyCFunction)start, METH_VARARGS, start_docstring},
	{"stop", (PyCFunction)stop, METH_VARARGS, stop_docstring},
	{NULL, NULL, NULL, NULL}
};

void 
init_sysevents_osx(void)
{
	Py_InitModule("_sysevents_osx", _sysevents_osx_methods);
}


